#include "BBS.h"
#include "Prngd.h"



BBS::BBS()
{
	mpz_init(p);
	mpz_init(q);
	mpz_init(m);
	mpz_init(r);
	mpz_init(output);
	mpz_init(output2);
	mpz_init(newSeed);
	byteOutput = NULL;
	useRandom = false;
}


BBS::~BBS()
{
	mpz_clear(p);
	mpz_clear(q);
	mpz_clear(m);
	mpz_clear(r);
	mpz_clear(output);
	mpz_clear(output2);
	mpz_clear(newSeed);
}


char* BBS::generateBits(mpz_t s, int bitCount, bool fastmode)
{
	int outputSize = bitCount / 8;
	int byteCount = 0;
	char tempChar = 0x00;
	int looprounds = 1;
	char* rndData = NULL;
	int shift = 0;

	//The number of bits is not a multiple of 8, so allocate a extra byte for the rest
	if( bitCount % 8 != 0)
		outputSize+=1;

	byteOutput = new char[outputSize+1]; //will be deleted through pointer in Prngd.cpp

	if(fastmode)
	{
		looprounds = bitCount /2048;
		if(bitCount % 2048 != 0)
			looprounds++;
	}

	for(int x= 0; x < looprounds ; x++)
	{

		do{
			if(fastmode)
				create_M(FAST_PART_SIZE);
			else
				create_M(bitCount);

			mpz_gcd(r, m, s);
		}
		while(mpz_cmp_ui(r, 1) != 0); //if this is not true, we where "lucky" and factored m by accident


		#ifdef DEBUG_MODE
			std::cout << "xi: ";
		#endif

		//for(int i = 1; i < bitCount+1 ; i++)
		for(int i = 1; i < 2048+1 ; i++)
		{
			mpz_powm_ui(output, s, 2, m); //output = s^2 mod m
			mpz_mod_ui(output2, output, 2); //output2 = output mod 2 (get the last bit as generated random bit)

			shift = i % 8;
			if(shift == 0)
				tempChar|= (unsigned char)(mpz_get_ui(output2));
			else
				tempChar|= (unsigned char)(mpz_get_ui(output2) << (8-shift));

			if(i%8 == 0)
			{
				byteOutput[byteCount] = tempChar;
				tempChar = 0x00;
				byteCount++;
			}

			#ifdef DEBUG_MODE
				std::cout << output2;
			#endif

			mpz_set(s, output);
		}
		rndData = Prngd::getRndData(2048, useRandom);
		mpz_set_str(newSeed, rndData, 2);
		delete rndData;
		mpz_xor(s, newSeed, s);

	}

	//write the rest into the output-array (if bitCount % 8 != 0)
	if(tempChar != ' ')
		byteOutput[byteCount] = tempChar;

	byteOutput[outputSize] = '\0'; //important for gmp lib to convert data into binary

	return byteOutput;

}



void BBS::getBlumPrime(mpz_t bigInt, int bits)
{
	char* rndData = NULL;
	rndData = Prngd::getRndData(bits, useRandom);
	mpz_set_str(bigInt, rndData, 2);


	#ifdef DEBUG_MODE
		std::cout << "\nPrePrimeValue: " << bigInt;
	#endif

	do
	{
		mpz_nextprime(bigInt, bigInt);
	}
	while(mpz_mod_ui(r, bigInt, 4) != 3);//check if the condition for a blum prime is  complied


	#ifdef DEBUG_MODE
		std::cout << "\nPrime : " << bigInt << "\n";
	#endif

	delete rndData;
}





void BBS::create_M(int bitCount)
{
	getBlumPrime(p, bitCount/2);
	getBlumPrime(q, bitCount-(bitCount/2)-1);
	mpz_mul(m, p, q);
	mpz_init(q);
	mpz_init(p);


	#ifdef DEBUG_MODE
		std::cout << "M ist: " << m << "\n";
	#endif
}



void BBS::printRndData(char* data, int size, bool binary)
{
	if(binary)
	{
		for(int i = 0; i < size; i++)
		{
			char temp_char = data[i];

			for(int j = 7; j >= 0; j--)
			{
				if(((temp_char >> j) & 1) == 1)
					std::cout << "1";
				else
					std::cout << "0";
			}
		}
	}
	else
	{
		std::cout << "Bytestream: ";
		for(int i = 0; i < size; i++)
			std::cout << data[i];
	}
}
